package com.bowlong.util;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

@SuppressWarnings("serial")
public class Table<K1, K2, V> implements Cloneable, Serializable {

	public final Map<K1, Map<K2, V>> rowMap = new HashMap<>();
	public final Map<K2, Map<K1, V>> columnMap = new HashMap<>();

	public Table() {
	}

	public Table(Table<K1, K2, V> table) {
		this(table.rowMap);
	}

	public Table(Map<K1, Map<K2, V>> rowMap) {
		Set<Entry<K1, Map<K2, V>>> ens = rowMap.entrySet();
		for (Entry<K1, Map<K2, V>> en : ens) {
			K1 k1 = en.getKey();
			Map<K2, V> va = en.getValue();
			Set<Entry<K2, V>> entrys = va.entrySet();
			for (Entry<K2, V> e : entrys) {
				K2 k2 = e.getKey();
				V v = e.getValue();
				put(k1, k2, v);
			}
		}
	}

	public void put(K1 rowKey, K2 colKey, V value) {
		Map<K2, V> row = rowMap.get(rowKey);
		if (row == null) {
			row = new HashMap<>();
			rowMap.put(rowKey, row);
		}
		row.put(colKey, value);

		Map<K1, V> col = columnMap.get(colKey);
		if (col == null) {
			col = new HashMap<>();
			columnMap.put(colKey, col);
		}
		col.put(rowKey, value);
	}

	public V get(K1 rowKey, K2 colKey) {
		Map<K2, V> row = rowMap.get(rowKey);
		if (row == null) {
			row = new HashMap<>();
			rowMap.put(rowKey, row);
			return null;
		}
		return row.get(colKey);
	}

	public Map<K2, V> row(K1 rowKey) {
		Map<K2, V> row = rowMap.get(rowKey);
		if (row == null) {
			row = new HashMap<>();
			rowMap.put(rowKey, row);
		}
		return new HashMap<>(row);
	}

	public Map<K1, V> column(K2 colKey) {
		Map<K1, V> col = columnMap.get(colKey);
		if (col == null) {
			col = new HashMap<>();
			columnMap.put(colKey, col);
		}
		return new HashMap<>(col);
	}

	public Map<K1, Map<K2, V>> rowMap() {
		return rowMap;
	}

	public Map<K2, Map<K1, V>> columnMap() {
		return columnMap;
	}

	public boolean contains(K1 rowKey, K2 colKey) {
		Map<K2, V> row = rowMap.get(rowKey);
		if (row == null) {
			row = new HashMap<>();
			rowMap.put(rowKey, row);
			return false;
		}
		return row.containsKey(colKey);
	}

	public boolean containsColumn(K2 colKey) {
		return columnMap.containsKey(colKey);
	}

	public boolean containsRow(K1 rowKey) {
		return rowMap.containsKey(rowKey);
	}

	public V remove(K1 rowKey, K2 colKey) {
		V r2 = null;
		Map<K2, V> row = rowMap.get(rowKey);
		if (row != null) {
			r2 = row.remove(colKey);
		}
		Map<K1, V> col = columnMap.get(colKey);
		if (col != null) {
			col.remove(rowKey);
		}
		return r2;
	}

	public Map<K2, V> removeRow(K1 rowKey) {
		Map<K2, V> row = rowMap.remove(rowKey);
		if (row == null) {
			row = new HashMap<>();
			rowMap.put(rowKey, row);
		} else {
			Set<K2> cols = row.keySet();
			for (K2 colKey : cols) {
				Map<K1, V> col = columnMap.get(colKey);
				if (col == null)
					continue;
				col.remove(rowKey);
			}
		}
		return new HashMap<>(row);
	}

	public Map<K1, V> removeCol(K2 colKey) {
		Map<K1, V> col = columnMap.remove(colKey);
		if (col == null) {
			col = new HashMap<>();
			columnMap.put(colKey, col);
		} else {
			Set<K1> rows = col.keySet();
			for (K1 rowKey : rows) {
				Map<K2, V> row = rowMap.get(rowKey);
				if (row == null)
					continue;
				row.remove(colKey);
			}
		}
		return new HashMap<>(col);
	}

	public void clear() {
		rowMap.clear();
		columnMap.clear();
	}

	public int rowSize() {
		return rowMap.size();
	}

	public int columnSize() {
		return columnMap.size();
	}

	public int size() {
		int r2 = 0;
		Collection<Map<K2, V>> columns = rowMap.values();
		for (Map<K2, V> map : columns) {
			r2 += map.size();
		}
		return r2;
	}

	@Override
	public String toString() {
		return "Table [rowMap=" + rowMap + "]";
	}

	public static void main(String[] args) {
		Table<Integer, Integer, String> tb = new Table<>();
		tb.put(1, 1, "11");
		tb.put(2, 1, "21");
		tb.put(1, 2, "12");
		tb.put(2, 2, "22");
		System.out.println(tb);
		System.out.println(tb.row(1));
		System.out.println(tb.column(2));
		System.out.println(tb.size());
		tb.remove(1, 2);
		System.out.println(tb);
		System.out.println(tb.row(1));
		System.out.println(tb.column(2));
		System.out.println(tb.size());
	}
}